﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel.Description;
using System.Windows.Forms;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Client;

namespace CrmConnectionProject
{
    #region Event Args Class Definition

    public class ConnectionSucceedEventArgs : EventArgs
    {
        public IOrganizationService OrganizationService { get; set; }
        public ConnectionDetail ConnectionDetail { get; set; }
        public object Parameter { get; set; } 
    }

    public class ConnectionFailedEventArgs : EventArgs
    {
        public string FailureReason { get; set; }
    }

    public class StepChangedEventArgs : EventArgs
    {
        public string CurrentStep { get; set; }
    }

    #endregion

    /// <summary>
    /// Manager that handles all connection operations
    /// </summary>
    public class ConnectionManager
    {
        #region Delegates

        public delegate void ConnectionSucceedEventHandler(object sender, ConnectionSucceedEventArgs e);
        public delegate void ConnectionFailedEventHandler(object sender, ConnectionFailedEventArgs e);
        public delegate void StepChangedEventHandler(object sender, StepChangedEventArgs e);

        #endregion

        #region Event Handlers

        public event ConnectionSucceedEventHandler ConnectionSucceed;
        public event ConnectionFailedEventHandler ConnectionFailed;
        public event StepChangedEventHandler StepChanged;

        #endregion

        #region Variables

        /// <summary>
        /// Form where this manager is used
        /// </summary>
        Form innerAppForm;

        /// <summary>
        /// List of Crm connections
        /// </summary>
        public CrmConnections ConnectionsList { get; set; }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of class ConnectionManager
        /// </summary>
        /// <param name="appForm">Form where this manager is used</param>
        public ConnectionManager(Form appForm)
        {
            this.innerAppForm = appForm;
            this.LoadConnectionsList();
            // allows for validation of SSL conversations
            ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateRemoteCertificate);

        }

        // callback used to validate the certificate in an SSL conversation
        private static bool ValidateRemoteCertificate(
        object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors policyErrors
        )
        {
            return true;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Asks this manager to select a Crm connection to use
        /// </summary>
        public void AskForConnection(object connectionParameter)
        {
            ConnectionSelector cs = new ConnectionSelector(LoadConnectionsList());
            cs.StartPosition = FormStartPosition.CenterParent;

            if (cs.ShowDialog(this.innerAppForm) == DialogResult.OK)
            {
                if (cs.HadCreatedNewConnection1)
                {
                    this.ConnectionsList.Connections.Add(cs.SelectedConnection);
                    this.SaveConnectionsFile();
                }

                ConnectToServer(cs.SelectedConnection, connectionParameter);
            }
        }

        /// <summary>
        /// Creates or updates a Crm connection
        /// </summary>
        /// <param name="isCreation">Indicates if it is a connection creation</param>
        /// <param name="connectionToUpdate">Details of the connection to update</param>
        /// <returns>Created or updated connection</returns>
        public ConnectionDetail EditConnection(bool isCreation, ConnectionDetail connectionToUpdate)
        {
            ConnectionForm cForm = new ConnectionForm(isCreation);
            cForm.StartPosition = FormStartPosition.CenterParent;

            if (!isCreation)
            {
                cForm.CrmConnectionDetail = connectionToUpdate;
            }

            if (cForm.ShowDialog(this.innerAppForm) == DialogResult.OK)
            {
                if (isCreation)
                {
                    this.ConnectionsList.Connections.Add(cForm.CrmConnectionDetail);
                }
                else
                {
                    foreach (ConnectionDetail detail in this.ConnectionsList.Connections)
                    {
                        if (detail.ConnectionId == cForm.CrmConnectionDetail.ConnectionId)
                        {
                            #region Update connection details

                            detail.ConnectionName = cForm.CrmConnectionDetail.ConnectionName;
                            detail.OrganizationServiceUrl = cForm.CrmConnectionDetail.OrganizationServiceUrl;
                            detail.CrmTicket = cForm.CrmConnectionDetail.CrmTicket;
                            detail.IsCustomAuth = cForm.CrmConnectionDetail.IsCustomAuth;
                            detail.Organization = cForm.CrmConnectionDetail.Organization;
                            detail.OrganizationFriendlyName = cForm.CrmConnectionDetail.OrganizationFriendlyName;
                            detail.ServerName = cForm.CrmConnectionDetail.ServerName;
                            detail.ServerPort = cForm.CrmConnectionDetail.ServerPort;
                            detail.UseIFD = cForm.CrmConnectionDetail.UseIFD;
                            detail.UseOnline = cForm.CrmConnectionDetail.UseOnline;
                            detail.UserDomain = cForm.CrmConnectionDetail.UserDomain;
                            detail.UserName = cForm.CrmConnectionDetail.UserName;
                            detail.UserPassword = cForm.CrmConnectionDetail.UserPassword;
                            detail.UseSsl = cForm.CrmConnectionDetail.UseSsl;

                            #endregion
                        }
                    }
                }

                this.SaveConnectionsFile();

                if (cForm.DoConnect)
                {
                    this.ConnectToServer(cForm.CrmConnectionDetail);
                }

                return cForm.CrmConnectionDetail;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Deletes a Crm connection from the connections list
        /// </summary>
        /// <param name="connectionToDelete">Details of the connection to delete</param>
        public void DeleteConnection(ConnectionDetail connectionToDelete)
        {
            this.ConnectionsList.Connections.Remove(connectionToDelete);
            this.SaveConnectionsFile();
        }

        /// <summary>
        /// Launch the Crm connection process 
        /// </summary>
        /// <param name="detail">Details of the Crm connection</param>
        /// <param name="connectionParameter">A parameter to retrieve after connection</param>
        public void ConnectToServer(ConnectionDetail detail, object connectionParameter)
        {
            List<object> parameters = new List<object>();
            parameters.Add(detail);
            parameters.Add(connectionParameter);

            // Runs the connection asynchronously
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.RunWorkerAsync(parameters);
        }

        /// <summary>
        /// Launch the Crm connection process 
        /// </summary>
        /// <param name="detail">Details of the Crm connection</param>
        public void ConnectToServer(ConnectionDetail detail)
        {
            this.ConnectToServer(detail, null);
        }

        /// <summary>
        /// Working process
        /// </summary>
        /// <param name="sender">BackgroundWorker object</param>
        /// <param name="e">BackgroundWorker object parameters</param>
        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            this.Connect((List<object>)e.Argument);
        }

        /// <summary>
        /// Connects to a Crm server
        /// </summary>
        /// <param name="oDetail">Details of the connection</param>
        private void Connect(List<object> parameters)
        {
            ConnectionDetail detail = (ConnectionDetail)parameters[0];

            // Shared variables
            ClientCredentials clientCredential = null;
            ClientCredentials deviceCredential = null;
            Uri uri = null;

            if (detail.UseOnline)
            {
                System.Net.HttpWebRequest.DefaultWebProxy = WebProxy.GetDefaultProxy();

                uri = new Uri(detail.OrganizationServiceUrl);

                if (!this.RequestPassword(detail))
                    return;

                clientCredential = new ClientCredentials();
                clientCredential.UserName.UserName = detail.UserName;
                clientCredential.UserName.Password = detail.UserPassword;

                this.SetCursor(this.innerAppForm, Cursors.WaitCursor);

                this.SendStepChange("Registering device on Windows Live Services...");

                try
                {
                    deviceCredential = DeviceIdManager.LoadOrRegisterDevice(new Guid());
                }
                catch (Exception error)
                {
                    this.SendFailureMessage(CrmExceptionHelper.GetErrorMessage(error, false));
                    return;
                }
            }
            else if (detail.UseIFD)
            {
                if (!this.RequestPassword(detail))
                    return;

                uri = new Uri(detail.OrganizationServiceUrl);

                clientCredential = new ClientCredentials();
                clientCredential.UserName.UserName = detail.UserName;
                clientCredential.UserName.Password = detail.UserPassword;
            }
            else
            {
                uri = new Uri(detail.OrganizationServiceUrl);

                clientCredential = new ClientCredentials();

                if (detail.IsCustomAuth)
                {
                    clientCredential.Windows.ClientCredential = new NetworkCredential(
                        detail.UserName,
                        detail.UserPassword,
                        detail.UserDomain);
                }
                else
                {
                    clientCredential.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
                }
            }

            // Connecting to Crm server
            try
            {
                this.SetCursor(this.innerAppForm, Cursors.WaitCursor);

                //WebProxy proxy = new WebProxy("tmggateway.alsy.fr:8080", true);
                //proxy.Credentials = new NetworkCredential("tanguy.touzard", "", "ste_alsy");
                //System.Net.HttpWebRequest.DefaultWebProxy = proxy;


                this.SendStepChange("Creating Organization service proxy...");
                OrganizationServiceProxy orgProxy = new OrganizationServiceProxy(uri, null, clientCredential, deviceCredential);
                // Thanks to rwilson504
                orgProxy.Timeout = new TimeSpan(10, 0, 0);

                IOrganizationService service = (IOrganizationService)orgProxy;

                this.SendSuccessMessage(service, parameters);
            }
            catch (Exception error)
            {
                this.SendFailureMessage(CrmExceptionHelper.GetErrorMessage(error, false));
            }
        }

        /// <summary>
        /// Checks the existence of a user password and returns it
        /// </summary>
        /// <param name="detail">Details of the Crm connection</param>
        /// <returns>True if password defined</returns>
        private bool RequestPassword(ConnectionDetail detail)
        {
            bool returnValue = false;

            PasswordForm pForm = new PasswordForm();
            pForm.UserLogin = detail.UserName;
            pForm.UserDomain = detail.UserDomain;
            pForm.StartPosition = FormStartPosition.CenterParent;

            MethodInvoker miShow = delegate
            {
                if (pForm.ShowDialog(this.innerAppForm) == DialogResult.OK)
                {
                    detail.UserPassword = pForm.UserPassword;
                    returnValue = true;
                }
            };

            if (!string.IsNullOrEmpty(detail.UserPassword))
                return true;

            if (this.innerAppForm.InvokeRequired)
            {
                this.innerAppForm.Invoke(miShow);
            }
            else
            {
                miShow();
            }

            return returnValue;
        }

        /// <summary>
        /// Restore Crm connections list from the file
        /// </summary>
        /// <returns>List of Crm connections</returns>
        public CrmConnections LoadConnectionsList()
        {
            if (File.Exists("mscrmtools2011.config"))
            {
                using (StreamReader configReader = new StreamReader("mscrmtools2011.config"))
                {
                    this.ConnectionsList = (CrmConnections)XmlSerializerHelper.Deserialize(configReader.ReadToEnd(), typeof(CrmConnections));
                }
            }
            else
            {
                ConnectionsList = new CrmConnections()
                {
                    Connections = new List<ConnectionDetail>()
                };
            }

            return this.ConnectionsList;
        }

        /// <summary>
        /// Saves Crm connections list to file
        /// </summary>
        public void SaveConnectionsFile()
        {
            XmlSerializerHelper.SerializeToFile(this.ConnectionsList, "mscrmtools2011.config");
        }

        /// <summary>
        /// Tests the specified connection
        /// </summary>
        /// <param name="service">Organization service</param>
        /// <param name="errorMessage">Error Message</param>
        public void TestConnection(IOrganizationService service, out string errorMessage)
        {
            try
            {
                WhoAmIRequest request = new WhoAmIRequest();
                WhoAmIResponse response = (WhoAmIResponse)service.Execute(request);

                errorMessage = string.Empty;
            }
            catch (Exception error)
            {
                errorMessage = CrmExceptionHelper.GetErrorMessage(error, false);
            }
        }
        #endregion

        #region Send Events

        /// <summary>
        /// Sends a connection success message 
        /// </summary>
        /// <param name="service">IOrganizationService generated</param>
        /// <param name="parameters">Lsit of parameter</param>
        private void SendSuccessMessage(IOrganizationService service, List<object> parameters)
        {
            if (this.ConnectionSucceed != null)
            {
                ConnectionSucceedEventArgs args = new ConnectionSucceedEventArgs()
                {
                    OrganizationService = service,
                    ConnectionDetail = (ConnectionDetail)parameters[0],
                    Parameter = parameters[1]
                };

                this.SetCursor(this.innerAppForm, Cursors.Default);
                
                this.ConnectionSucceed(this, args);
           
                this.SetCursor(this.innerAppForm, Cursors.Default);
            }
        }

        /// <summary>
        /// Sends a connection failure message
        /// </summary>
        /// <param name="failureReason">Reason of the failure</param>
        private void SendFailureMessage(string failureReason)
        {
            if (this.ConnectionFailed != null)
            {
                ConnectionFailedEventArgs args = new ConnectionFailedEventArgs()
                {
                    FailureReason = failureReason
                };

                this.SetCursor(this.innerAppForm, Cursors.Default);

                this.ConnectionFailed(this, args);
            
                this.SetCursor(this.innerAppForm, Cursors.Default);
            }
        }

        /// <summary>
        /// Sends a step change message
        /// </summary>
        /// <param name="step">New step</param>
        private void SendStepChange(string step)
        {
            StepChangedEventArgs args = new StepChangedEventArgs()
            {
                CurrentStep = step
            };

            if (this.StepChanged != null)
            {
                this.StepChanged(this, args);
            }
        }
 
        #endregion

        #region Async Delegates

        void SetCursor(Form form, Cursor cursor)
        {
            MethodInvoker mi = delegate
            {
                form.Cursor = cursor;
            };

            if(form.InvokeRequired)
            {
                form.Invoke(mi);
            }
            else
            {
                mi();
            }
        }

        #endregion
    }
}
